

const canvas = document.getElementById("canvas")
const context = canvas.getContext("2d")

const WIDTH = 600
const HEIGHT = 800

canvas.width = WIDTH
canvas.height = HEIGHT

context.scale(20, 20)  //创建的物体在坐标系x，y方向扩大20倍

//七种经典方块

const metronomes = {
  I: [
    [1, 0, 0, 0],
    [1, 0, 0, 0],
    [1, 0, 0, 0],
    [1, 0, 0, 0],
  ],
  O: [
    [2, 2],
    [2, 2],
  ],
  L: [
    [3, 0, 0],
    [3, 0, 0],
    [3, 3, 0],
  ],
  J: [
    [4, 4, 0],
    [4, 0, 0],
    [4, 0, 0],
  ],
  S: [
    [5, 0, 0],
    [5, 5, 0],
    [0, 5, 0],
  ],
  Z: [
    [0, 6, 0],
    [6, 6, 0],
    [6, 0, 0],
  ],
  T: [
    [0, 0, 0],
    [7, 7, 7],
    [0, 7, 0],
  ],
}

const colors = [
  "",
  "#2b39a3",
  "#e6e34f",
  "#e99a50",
  "#dd3b9c",
  "#4fd388",
  "#d13f3d",
  "#4bbad3",
]



/**
 * 创建方块和创建并重置点坐标
 * @returns {{pos: {x: number, y: number}, matrix: model}}
 */
function createPlayer() {
  return {
    pos: {
      x: 0,
      y: 0,
    },
    matrix: createPlayerMatrix(),
  }
}
/**
 * 获取随机的下落方块模型
 * @returns
 */
function createPlayerMatrix() {
  const index = Math.floor(Object.keys(metronomes).length * Math.random()) //随机出0-6
  return metronomes[Object.keys(metronomes)[index]]
}

//创建下落模块模型（包括绘制参考点，方块模型）
let player = createPlayer()  //随机返回一个对象 0，0点坐标，和随机的一个方块二维数组


/**
 * 生成二维数组  方块的移动区域  二维数组
 * @param {*} w 画布宽度
 * @param {*} h 画布高度
 * @returns
 */
function createMatrix(w, h) {
  return Array.from({length: h / 20}, () => (Array.from({length: w / 20}, () => (0))));
}

//创建移动区域
let arena = createMatrix(600, 800) //移动区域

let lastTime = 0
let dropCounter = 0
let dropInterval = 1000

let requestAnimationFrameId = null  //渲染桢 id



/**
 * 浏览器每一帧要执行的函数
 * @param time 执行当前函数距离第一次函数执行的时间戳（毫秒数）
 */
function update(time = 0) {
  let deltaTime = time - lastTime
  dropCounter += deltaTime
  if (dropCounter > dropInterval) {
    playerDrop()
    dropCounter = 0
  }
  lastTime = time
  draw()
  requestAnimationFrameId = requestAnimationFrame(update)
}
update()//预先执行一次

/**
 * 监听键盘事件
 */
window.addEventListener("keydown", (e) => {
  //没有渲染函数直接返回
  if (!requestAnimationFrameId) return
  switch (e.key) {
    case "ArrowLeft": { //左键
      const nextPlayerPos = {
        ...player,
        pos: {
          ...player.pos,
          x: player.pos.x - 1,
        },
      }
      if (collide(arena, nextPlayerPos)) break
      player.pos.x--
      break
    }
    case "ArrowRight": {  // 右键
      const nextPlayerPos = {
        ...player,
        pos: {
          ...player.pos,
          x: player.pos.x + 1,
        },
      }
      if (collide(arena, nextPlayerPos)) break
      player.pos.x++
      break
    }
    case "ArrowDown": {  //下键
      const nextPlayerPos = {
        ...player,
        pos: {
          ...player.pos,
          y: player.pos.y + 1,
        },
      }
      if (collide(arena, nextPlayerPos)) break
      player.pos = nextPlayerPos.pos
      break
    }
    case "ArrowUp": {  //上键
      rotate(player.matrix)

      break
    }
  }
})




/**
 * 旋转方块
 * @param model
 */
function rotate(model) {
  const length = model.length;
  const result = Array.from({length}, () => ([]));
  for (let i = 0; i < length; i++) {
    for (let j = 0; j < model[i].length; j++) {
      result[j].unshift(model[i][j])
    }
  }
  const nextPlayer = {
    ...player,
    matrix: result,
  }
  if (collide(arena, nextPlayer)) return
  player = nextPlayer
}

/**
 * 碰撞之后，将该方块添加到游戏区域
 * @param arena 游戏区域
 * @param player 移动方块
 */
function merge(arena, player) {
  for (let i = 0; i < player.matrix.length; i++) {
    for (let j = 0; j < player.matrix[i].length; j++) {
      if (player.matrix[i][j] !== 0) {
        arena[player.pos.y + i][player.pos.x + j] = player.matrix[i][j]
      }
    }
  }
}

/**
 * 画方块到画布上
 * @param matrix  方块
 * @param offset  参考点
 */
function drawMatrix(matrix, offset) {
  matrix.forEach((row, y) => {
    row.forEach((value, x) => {
      if (value !== 0) {
        pickColor(value)
        context.fillRect(
          x + offset.x + 0.1,
          y + offset.y + 0.1,
          1 - 0.2,
          1 - 0.2
        )

        context.strokeStyle = "#fff"
        context.lineWidth = 0.1
        context.strokeRect(x + offset.x, y + offset.y, 1, 1)
      }
    })
  })
}

/**
 * 方块模型生成随机颜色
 * @param value
 */
function pickColor(value) {
  context.fillStyle = colors[value]
}


/**
 * 重置方块
 */
function resetPlayer() {
  player.pos.x = 0
  player.pos.y = 0
  player.matrix = createPlayerMatrix()
  isOver()
}



/**
 * 画出方块到画布上
 */
function draw() {
  resetCanvas()
  drawMatrix(player.matrix, player.pos)
  drawMatrix(arena, { x: 0, y: 0 })
  drawNet()
}

/**
 * 用于处理方块处于一条线的消除方法
 * @param arena  游戏区域
 */
function clearLine(arena) {
  for (let i = 0; i < arena.length; i++) {
    for (let j = 0; j < arena[i].length; j++) {
      if (arena[i][j] === 0) break
      if (j === arena[i].length - 1) {
        //删除该行，并填充一条数据到游戏区域开头
        arena.unshift(arena.splice(i, 1)[0].fill(0))
      }
    }
  }
}

/**
 * 用于重置清除画布
 */
function resetCanvas() {
  context.clearRect(0, 0, WIDTH, HEIGHT)
  context.fillStyle = "#000000"
  context.fillRect(0, 0, WIDTH, HEIGHT)
}

/**
 * 网格线
 */
function drawNet() {
  context.strokeStyle = "#ffffff16"
  context.lineWidth = 0.1
  for (let i = 1; i < 30; i++) {
    context.beginPath()
    context.moveTo(i, 0)
    context.lineTo(i, 40)
    context.stroke()
  }
  for (let i = 1; i < 40; i++) {
    context.beginPath()
    context.moveTo(0, i)
    context.lineTo(30, i)
    context.stroke()
  }
}





/**
 * 处理方块下落的方法
 */
function playerDrop() {
  const nextPlayerPos = {
    ...player,
    pos: {
      ...player.pos,
      y: player.pos.y + 1,
    },
  }
 
  if (collide(arena, nextPlayerPos)) {
    merge(arena, player)
    clearLine(arena)
    resetPlayer()
  } else {
    player.pos = nextPlayerPos.pos
  }
}

/**
 * 碰撞检测
 * @param arena  游戏区域
 * @param player 方块
 * @returns {boolean}
 */
function collide(arena, player) {
  for (let i = 0; i < player.matrix.length; i++) {
    for (let j = 0; j < player.matrix[i].length; j++) {
      // 方块不存在跳过本次循环
      if (!player.matrix[i][j]) continue

      //方块下一次要出现的位置已经超出游戏区域
      if (
        arena[player.pos.y + i] === undefined ||
        arena[player.pos.y + i][player.pos.x + j] === undefined
      ) {
        return true
      }

      //方块下一次出现的位置已经有其他方块
      if (
        arena[player.pos.y + i] &&
        arena[player.pos.y + i][player.pos.x + j]
      ) {
        return true
      }
    }
  }
  return false
}

/**
 * 游戏结束，方块积累
 */
function isOver() {
  if (player.pos.y === 0 && collide(arena, player)) {
    alert("游戏结束")
    init()
  }
}

/**
 * 游戏失败后，重新创建游戏区域和创建方块并初始化参考点
 */
function init() {
  arena = createMatrix(30, 40)
  player = createPlayer()
}